// Copyright: (2012-2015) Ben Strasser <code@ben-strasser.net>
// License: BSD-3
//
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice,
//    this list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice,
//    this list of conditions and the following disclaimer in the documentation
//    and/or other materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors
//    may be used to endorse or promote products derived from this software
//    without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.

#ifndef CSV_H
#define CSV_H

#include <vector>
#include <string>
#include <cstring>
#include <algorithm>
#include <utility>
#include <cstdio>
#include <exception>
#ifndef CSV_IO_NO_THREAD
#include <mutex>
#include <thread>
#include <condition_variable>
#endif
#include <memory>
#include <cassert>
#include <cerrno>
#include <istream>
#include <limits>

namespace io{
    ////////////////////////////////////////////////////////////////////////////
    //                                 LineReader                             //
    ////////////////////////////////////////////////////////////////////////////

    namespace error{
        struct base : std::exception{
            virtual void format_error_message()const = 0;

            const char*what()const noexcept override{
                format_error_message();
                return error_message_buffer;
            }

            mutable char error_message_buffer[512];
        };

        const int max_file_name_length = 255;

        struct with_file_name{
            with_file_name(){
                std::memset(file_name, 0, sizeof(file_name));
            }

            void set_file_name(const char*file_name){
                if(file_name != nullptr){
                    // This call to strncpy has parenthesis around it
                    // to silence the GCC -Wstringop-truncation warning
                    (strncpy(this->file_name, file_name, sizeof(this->file_name)));
                    this->file_name[sizeof(this->file_name)-1] = '\0';
                }else{
                    this->file_name[0] = '\0';
                }
            }

            char file_name[max_file_name_length+1];
        };

        struct with_file_line{
            with_file_line(){
                file_line = -1;
            }

            void set_file_line(int file_line){
                this->file_line = file_line;
            }

            int file_line;
        };

        struct with_errno{
            with_errno(){
                errno_value = 0;
            }

            void set_errno(int errno_value){
                this->errno_value = errno_value;
            }

            int errno_value;
        };

        struct can_not_open_file :
                base,
                with_file_name,
                with_errno{
            void format_error_message()const override{
                if(errno_value != 0)
                    std::snprintf(error_message_buffer, sizeof(error_message_buffer),
                                  "Can not open file \"%s\" because \"%s\"."
                            , file_name, std::strerror(errno_value));
                else
                    std::snprintf(error_message_buffer, sizeof(error_message_buffer),
                                  "Can not open file \"%s\"."
                            , file_name);
            }
        };

        struct line_length_limit_exceeded :
                base,
                with_file_name,
                with_file_line{
            void format_error_message()const override{
                std::snprintf(error_message_buffer, sizeof(error_message_buffer),
                              "Line number %d in file \"%s\" exceeds the maximum length of 2^24-1."
                        , file_line, file_name);
            }
        };
    }

    class ByteSourceBase{
    public:
        virtual int read(char*buffer, int size)=0;
        virtual ~ByteSourceBase(){}
    };

    namespace detail{

        class OwningStdIOByteSourceBase : public ByteSourceBase{
        public:
            explicit OwningStdIOByteSourceBase(FILE*file):file(file){
                // Tell the std library that we want to do the buffering ourself.
                std::setvbuf(file, 0, _IONBF, 0);
            }

            int read(char*buffer, int size){
                return std::fread(buffer, 1, size, file);
            }

            ~OwningStdIOByteSourceBase(){
                std::fclose(file);
            }

        private:
            FILE*file;
        };

        class NonOwningIStreamByteSource : public ByteSourceBase{
        public:
            explicit NonOwningIStreamByteSource(std::istream&in):in(in){}

            int read(char*buffer, int size){
                in.read(buffer, size);
                return in.gcount();
            }

            ~NonOwningIStreamByteSource(){}

        private:
            std::istream&in;
        };

        class NonOwningStringByteSource : public ByteSourceBase{
        public:
            NonOwningStringByteSource(const char*str, long long size):str(str), remaining_byte_count(size){}

            int read(char*buffer, int desired_byte_count){
                int to_copy_byte_count = desired_byte_count;
                if(remaining_byte_count < to_copy_byte_count)
                    to_copy_byte_count = remaining_byte_count;
                std::memcpy(buffer, str, to_copy_byte_count);
                remaining_byte_count -= to_copy_byte_count;
                str += to_copy_byte_count;
                return to_copy_byte_count;
            }

            ~NonOwningStringByteSource(){}

        private:
            const char*str;
            long long remaining_byte_count;
        };

#ifndef CSV_IO_NO_THREAD
        class AsynchronousReader{
        public:
            void init(std::unique_ptr<ByteSourceBase>arg_byte_source){
                std::unique_lock<std::mutex>guard(lock);
                byte_source = std::move(arg_byte_source);
                desired_byte_count = -1;
                termination_requested = false;
                worker = std::thread(
                        [&]{
                            std::unique_lock<std::mutex>guard(lock);
                            try{
                                for(;;){
                                    read_requested_condition.wait(
                                            guard,
                                            [&]{
                                                return desired_byte_count != -1 || termination_requested;
                                            }
                                    );
                                    if(termination_requested)
                                        return;

                                    read_byte_count = byte_source->read(buffer, desired_byte_count);
                                    desired_byte_count = -1;
                                    if(read_byte_count == 0)
                                        break;
                                    read_finished_condition.notify_one();
                                }
                            }catch(...){
                                read_error = std::current_exception();
                            }
                            read_finished_condition.notify_one();
                        }
                );
            }

            bool is_valid()const{
                return byte_source != nullptr;
            }

            void start_read(char*arg_buffer, int arg_desired_byte_count){
                std::unique_lock<std::mutex>guard(lock);
                buffer = arg_buffer;
                desired_byte_count = arg_desired_byte_count;
                read_byte_count = -1;
                read_requested_condition.notify_one();
            }

            int finish_read(){
                std::unique_lock<std::mutex>guard(lock);
                read_finished_condition.wait(
                        guard,
                        [&]{
                            return read_byte_count != -1 || read_error;
                        }
                );
                if(read_error)
                    std::rethrow_exception(read_error);
                else
                    return read_byte_count;
            }

            ~AsynchronousReader(){
                if(byte_source != nullptr){
                    {
                        std::unique_lock<std::mutex>guard(lock);
                        termination_requested = true;
                    }
                    read_requested_condition.notify_one();
                    worker.join();
                }
            }

        private:
            std::unique_ptr<ByteSourceBase>byte_source;

            std::thread worker;

            bool termination_requested;
            std::exception_ptr read_error;
            char*buffer;
            int desired_byte_count;
            int read_byte_count;

            std::mutex lock;
            std::condition_variable read_finished_condition;
            std::condition_variable read_requested_condition;
        };
#endif

        class SynchronousReader{
        public:
            void init(std::unique_ptr<ByteSourceBase>arg_byte_source){
                byte_source = std::move(arg_byte_source);
            }

            bool is_valid()const{
                return byte_source != nullptr;
            }

            void start_read(char*arg_buffer, int arg_desired_byte_count){
                buffer = arg_buffer;
                desired_byte_count = arg_desired_byte_count;
            }

            int finish_read(){
                return byte_source->read(buffer, desired_byte_count);
            }
        private:
            std::unique_ptr<ByteSourceBase>byte_source;
            char*buffer;
            int desired_byte_count;
        };
    }

    class LineReader{
    private:
        static const int block_len = 1<<20;
        std::unique_ptr<char[]>buffer; // must be constructed before (and thus destructed after) the reader!
#ifdef CSV_IO_NO_THREAD
        detail::SynchronousReader reader;
#else
        detail::AsynchronousReader reader;
#endif
        int data_begin;
        int data_end;

        char file_name[error::max_file_name_length+1];
        unsigned file_line;

        static std::unique_ptr<ByteSourceBase> open_file(const char*file_name){
            // We open the file in binary mode as it makes no difference under *nix
            // and under Windows we handle \r\n newlines ourself.
            FILE*file = std::fopen(file_name, "rb");
            if(file == 0){
                int x = errno; // store errno as soon as possible, doing it after constructor call can fail.
                error::can_not_open_file err;
                err.set_errno(x);
                err.set_file_name(file_name);
                throw err;
            }
            return std::unique_ptr<ByteSourceBase>(new detail::OwningStdIOByteSourceBase(file));
        }

        void init(std::unique_ptr<ByteSourceBase>byte_source){
            file_line = 0;

            buffer = std::unique_ptr<char[]>(new char[3*block_len]);
            data_begin = 0;
            data_end = byte_source->read(buffer.get(), 2*block_len);

            // Ignore UTF-8 BOM
            if(data_end >= 3 && buffer[0] == '\xEF' && buffer[1] == '\xBB' && buffer[2] == '\xBF')
                data_begin = 3;

            if(data_end == 2*block_len){
                reader.init(std::move(byte_source));
                reader.start_read(buffer.get() + 2*block_len, block_len);
            }
        }

    public:
        LineReader() = delete;
        LineReader(const LineReader&) = delete;
        LineReader&operator=(const LineReader&) = delete;

        explicit LineReader(const char*file_name){
            set_file_name(file_name);
            init(open_file(file_name));
        }

        explicit LineReader(const std::string&file_name){
            set_file_name(file_name.c_str());
            init(open_file(file_name.c_str()));
        }

        LineReader(const char*file_name, std::unique_ptr<ByteSourceBase>byte_source){
            set_file_name(file_name);
            init(std::move(byte_source));
        }

        LineReader(const std::string&file_name, std::unique_ptr<ByteSourceBase>byte_source){
            set_file_name(file_name.c_str());
            init(std::move(byte_source));
        }

        LineReader(const char*file_name, const char*data_begin, const char*data_end){
            set_file_name(file_name);
            init(std::unique_ptr<ByteSourceBase>(new detail::NonOwningStringByteSource(data_begin, data_end-data_begin)));
        }

        LineReader(const std::string&file_name, const char*data_begin, const char*data_end){
            set_file_name(file_name.c_str());
            init(std::unique_ptr<ByteSourceBase>(new detail::NonOwningStringByteSource(data_begin, data_end-data_begin)));
        }

        LineReader(const char*file_name, FILE*file){
            set_file_name(file_name);
            init(std::unique_ptr<ByteSourceBase>(new detail::OwningStdIOByteSourceBase(file)));
        }

        LineReader(const std::string&file_name, FILE*file){
            set_file_name(file_name.c_str());
            init(std::unique_ptr<ByteSourceBase>(new detail::OwningStdIOByteSourceBase(file)));
        }

        LineReader(const char*file_name, std::istream&in){
            set_file_name(file_name);
            init(std::unique_ptr<ByteSourceBase>(new detail::NonOwningIStreamByteSource(in)));
        }

        LineReader(const std::string&file_name, std::istream&in){
            set_file_name(file_name.c_str());
            init(std::unique_ptr<ByteSourceBase>(new detail::NonOwningIStreamByteSource(in)));
        }

        void set_file_name(const std::string&file_name){
            set_file_name(file_name.c_str());
        }

        void set_file_name(const char*file_name){
            if(file_name != nullptr){
                strncpy(this->file_name, file_name, sizeof(this->file_name));
                this->file_name[sizeof(this->file_name)-1] = '\0';
            }else{
                this->file_name[0] = '\0';
            }
        }

        const char*get_truncated_file_name()const{
            return file_name;
        }

        void set_file_line(unsigned file_line){
            this->file_line = file_line;
        }

        unsigned get_file_line()const{
            return file_line;
        }

        char*next_line(){
            if(data_begin == data_end)
                return nullptr;

            ++file_line;

            assert(data_begin < data_end);
            assert(data_end <= block_len*2);

            if(data_begin >= block_len){
                std::memcpy(buffer.get(), buffer.get()+block_len, block_len);
                data_begin -= block_len;
                data_end -= block_len;
                if(reader.is_valid())
                {
                    data_end += reader.finish_read();
                    std::memcpy(buffer.get()+block_len, buffer.get()+2*block_len, block_len);
                    reader.start_read(buffer.get() + 2*block_len, block_len);
                }
            }

            int line_end = data_begin;
            while(line_end != data_end && buffer[line_end] != '\n'){
                ++line_end;
            }

            if(line_end - data_begin + 1 > block_len){
                error::line_length_limit_exceeded err;
                err.set_file_name(file_name);
                err.set_file_line(file_line);
                throw err;
            }

            if(line_end != data_end && buffer[line_end] == '\n'){
                buffer[line_end] = '\0';
            }else{
                // some files are missing the newline at the end of the
                // last line
                ++data_end;
                buffer[line_end] = '\0';
            }

            // handle windows \r\n-line breaks
            if(line_end != data_begin && buffer[line_end-1] == '\r')
                buffer[line_end-1] = '\0';

            char*ret = buffer.get() + data_begin;
            data_begin = line_end+1;
            return ret;
        }
    };


    ////////////////////////////////////////////////////////////////////////////
    //                                 CSV                                    //
    ////////////////////////////////////////////////////////////////////////////

    namespace error{
        const int max_column_name_length = 63;
        struct with_column_name{
            with_column_name(){
                std::memset(column_name, 0, max_column_name_length+1);
            }

            void set_column_name(const char*column_name){
                if(column_name != nullptr){
                    std::strncpy(this->column_name, column_name, max_column_name_length);
                    this->column_name[max_column_name_length] = '\0';
                }else{
                    this->column_name[0] = '\0';
                }
            }

            char column_name[max_column_name_length+1];
        };


        const int max_column_content_length = 63;

        struct with_column_content{
            with_column_content(){
                std::memset(column_content, 0, max_column_content_length+1);
            }

            void set_column_content(const char*column_content){
                if(column_content != nullptr){
                    std::strncpy(this->column_content, column_content, max_column_content_length);
                    this->column_content[max_column_content_length] = '\0';
                }else{
                    this->column_content[0] = '\0';
                }
            }

            char column_content[max_column_content_length+1];
        };


        struct extra_column_in_header :
                base,
                with_file_name,
                with_column_name{
            void format_error_message()const override{
                std::snprintf(error_message_buffer, sizeof(error_message_buffer),
                              R"(Extra column "%s" in header of file "%s".)"
                        , column_name, file_name);
            }
        };

        struct missing_column_in_header :
                base,
                with_file_name,
                with_column_name{
            void format_error_message()const override{
                std::snprintf(error_message_buffer, sizeof(error_message_buffer),
                              R"(Missing column "%s" in header of file "%s".)"
                        , column_name, file_name);
            }
        };

        struct duplicated_column_in_header :
                base,
                with_file_name,
                with_column_name{
            void format_error_message()const override{
                std::snprintf(error_message_buffer, sizeof(error_message_buffer),
                              R"(Duplicated column "%s" in header of file "%s".)"
                        , column_name, file_name);
            }
        };

        struct header_missing :
                base,
                with_file_name{
            void format_error_message()const override{
                std::snprintf(error_message_buffer, sizeof(error_message_buffer),
                              "Header missing in file \"%s\"."
                        , file_name);
            }
        };

        struct too_few_columns :
                base,
                with_file_name,
                with_file_line{
            void format_error_message()const override{
                std::snprintf(error_message_buffer, sizeof(error_message_buffer),
                              "Too few columns in line %d in file \"%s\"."
                        , file_line, file_name);
            }
        };

        struct too_many_columns :
                base,
                with_file_name,
                with_file_line{
            void format_error_message()const override{
                std::snprintf(error_message_buffer, sizeof(error_message_buffer),
                              "Too many columns in line %d in file \"%s\"."
                        , file_line, file_name);
            }
        };

        struct escaped_string_not_closed :
                base,
                with_file_name,
                with_file_line{
            void format_error_message()const override{
                std::snprintf(error_message_buffer, sizeof(error_message_buffer),
                              "Escaped string was not closed in line %d in file \"%s\"."
                        , file_line, file_name);
            }
        };

        struct integer_must_be_positive :
                base,
                with_file_name,
                with_file_line,
                with_column_name,
                with_column_content{
            void format_error_message()const override{
                std::snprintf(error_message_buffer, sizeof(error_message_buffer),
                              R"(The integer "%s" must be positive or 0 in column "%s" in file "%s" in line "%d".)"
                        , column_content, column_name, file_name, file_line);
            }
        };

        struct no_digit :
                base,
                with_file_name,
                with_file_line,
                with_column_name,
                with_column_content{
            void format_error_message()const override{
                std::snprintf(error_message_buffer, sizeof(error_message_buffer),
                              R"(The integer "%s" contains an invalid digit in column "%s" in file "%s" in line "%d".)"
                        , column_content, column_name, file_name, file_line);
            }
        };

        struct integer_overflow :
                base,
                with_file_name,
                with_file_line,
                with_column_name,
                with_column_content{
            void format_error_message()const override{
                std::snprintf(error_message_buffer, sizeof(error_message_buffer),
                              R"(The integer "%s" overflows in column "%s" in file "%s" in line "%d".)"
                        , column_content, column_name, file_name, file_line);
            }
        };

        struct integer_underflow :
                base,
                with_file_name,
                with_file_line,
                with_column_name,
                with_column_content{
            void format_error_message()const override{
                std::snprintf(error_message_buffer, sizeof(error_message_buffer),
                              R"(The integer "%s" underflows in column "%s" in file "%s" in line "%d".)"
                        , column_content, column_name, file_name, file_line);
            }
        };

        struct invalid_single_character :
                base,
                with_file_name,
                with_file_line,
                with_column_name,
                with_column_content{
            void format_error_message()const override{
                std::snprintf(error_message_buffer, sizeof(error_message_buffer),
                              R"(The content "%s" of column "%s" in file "%s" in line "%d" is not a single character.)"
                        , column_content, column_name, file_name, file_line);
            }
        };
    }

    using ignore_column = unsigned int;
    static const ignore_column ignore_no_column = 0;
    static const ignore_column ignore_extra_column = 1;
    static const ignore_column ignore_missing_column = 2;

    template<char ... trim_char_list>
    struct trim_chars{
    private:
        constexpr static bool is_trim_char(char){
            return false;
        }

        template<class ...OtherTrimChars>
        constexpr static bool is_trim_char(char c, char trim_char, OtherTrimChars...other_trim_chars){
            return c == trim_char || is_trim_char(c, other_trim_chars...);
        }

    public:
        static void trim(char*&str_begin, char*&str_end){
            while(str_begin != str_end && is_trim_char(*str_begin, trim_char_list...))
                ++str_begin;
            while(str_begin != str_end && is_trim_char(*(str_end-1), trim_char_list...))
                --str_end;
            *str_end = '\0';
        }
    };


    struct no_comment{
        static bool is_comment(const char*){
            return false;
        }
    };

    template<char ... comment_start_char_list>
    struct single_line_comment{
    private:
        constexpr static bool is_comment_start_char(char){
            return false;
        }

        template<class ...OtherCommentStartChars>
        constexpr static bool is_comment_start_char(char c, char comment_start_char, OtherCommentStartChars...other_comment_start_chars){
            return c == comment_start_char || is_comment_start_char(c, other_comment_start_chars...);
        }

    public:

        static bool is_comment(const char*line){
            return is_comment_start_char(*line, comment_start_char_list...);
        }
    };

    struct empty_line_comment{
        static bool is_comment(const char*line){
            if(*line == '\0')
                return true;
            while(*line == ' ' || *line == '\t'){
                ++line;
                if(*line == 0)
                    return true;
            }
            return false;
        }
    };

    template<char ... comment_start_char_list>
    struct single_and_empty_line_comment{
        static bool is_comment(const char*line){
            return single_line_comment<comment_start_char_list...>::is_comment(line) || empty_line_comment::is_comment(line);
        }
    };

    template<char sep>
    struct no_quote_escape{
        static const char*find_next_column_end(const char*col_begin){
            while(*col_begin != sep && *col_begin != '\0')
                ++col_begin;
            return col_begin;
        }

        static void unescape(char*&, char*&){

        }
    };

    template<char sep, char quote>
    struct double_quote_escape{
        static const char*find_next_column_end(const char*col_begin){
            while(*col_begin != sep && *col_begin != '\0')
                if(*col_begin != quote)
                    ++col_begin;
                else{
                    do{
                        ++col_begin;
                        while(*col_begin != quote){
                            if(*col_begin == '\0')
                                throw error::escaped_string_not_closed();
                            ++col_begin;
                        }
                        ++col_begin;
                    }while(*col_begin == quote);
                }
            return col_begin;
        }

        static void unescape(char*&col_begin, char*&col_end){
            if(col_end - col_begin >= 2){
                if(*col_begin == quote && *(col_end-1) == quote){
                    ++col_begin;
                    --col_end;
                    char*out = col_begin;
                    for(char*in = col_begin; in!=col_end; ++in){
                        if(*in == quote && (in+1) != col_end && *(in+1) == quote){
                            ++in;
                        }
                        *out = *in;
                        ++out;
                    }
                    col_end = out;
                    *col_end = '\0';
                }
            }

        }
    };

    struct throw_on_overflow{
        template<class T>
        static void on_overflow(T&){
            throw error::integer_overflow();
        }

        template<class T>
        static void on_underflow(T&){
            throw error::integer_underflow();
        }
    };

    struct ignore_overflow{
        template<class T>
        static void on_overflow(T&){}

        template<class T>
        static void on_underflow(T&){}
    };

    struct set_to_max_on_overflow{
        template<class T>
        static void on_overflow(T&x){
            // using (std::numeric_limits<T>::max) instead of std::numeric_limits<T>::max
            // to make code including windows.h with its max macro happy
            x = (std::numeric_limits<T>::max)();
        }

        template<class T>
        static void on_underflow(T&x){
            x = (std::numeric_limits<T>::min)();
        }
    };


    namespace detail{
        template<class quote_policy>
        void chop_next_column(
                char*&line, char*&col_begin, char*&col_end
        ){
            assert(line != nullptr);

            col_begin = line;
            // the col_begin + (... - col_begin) removes the constness
            col_end = col_begin + (quote_policy::find_next_column_end(col_begin) - col_begin);

            if(*col_end == '\0'){
                line = nullptr;
            }else{
                *col_end = '\0';
                line = col_end + 1;
            }
        }

        template<class trim_policy, class quote_policy>
        void parse_line(
                char*line,
                char**sorted_col,
                const std::vector<int>&col_order
        ){
            for (int i : col_order) {
                if(line == nullptr)
                    throw ::io::error::too_few_columns();
                char*col_begin, *col_end;
                chop_next_column<quote_policy>(line, col_begin, col_end);

                if (i != -1) {
                    trim_policy::trim(col_begin, col_end);
                    quote_policy::unescape(col_begin, col_end);

                    sorted_col[i] = col_begin;
                }
            }
            if(line != nullptr)
                throw ::io::error::too_many_columns();
        }

        template<unsigned column_count, class trim_policy, class quote_policy>
        void parse_header_line(
                char*line,
                std::vector<int>&col_order,
                const std::string*col_name,
                ignore_column ignore_policy
        ){
            col_order.clear();

            bool found[column_count];
            std::fill(found, found + column_count, false);
            while(line){
                char*col_begin,*col_end;
                chop_next_column<quote_policy>(line, col_begin, col_end);

                trim_policy::trim(col_begin, col_end);
                quote_policy::unescape(col_begin, col_end);

                for(unsigned i=0; i<column_count; ++i)
                    if(col_begin == col_name[i]){
                        if(found[i]){
                            error::duplicated_column_in_header err;
                            err.set_column_name(col_begin);
                            throw err;
                        }
                        found[i] = true;
                        col_order.push_back(i);
                        col_begin = 0;
                        break;
                    }
                if(col_begin){
                    if(ignore_policy & ::io::ignore_extra_column)
                        col_order.push_back(-1);
                    else{
                        error::extra_column_in_header err;
                        err.set_column_name(col_begin);
                        throw err;
                    }
                }
            }
            if(!(ignore_policy & ::io::ignore_missing_column)){
                for(unsigned i=0; i<column_count; ++i){
                    if(!found[i]){
                        error::missing_column_in_header err;
                        err.set_column_name(col_name[i].c_str());
                        throw err;
                    }
                }
            }
        }

        template<class overflow_policy>
        void parse(char*col, char &x){
            if(!*col)
                throw error::invalid_single_character();
            x = *col;
            ++col;
            if(*col)
                throw error::invalid_single_character();
        }

        template<class overflow_policy>
        void parse(char*col, std::string&x){
            x = col;
        }

        template<class overflow_policy>
        void parse(char*col, const char*&x){
            x = col;
        }

        template<class overflow_policy>
        void parse(char*col, char*&x){
            x = col;
        }

        template<class overflow_policy, class T>
        void parse_unsigned_integer(const char*col, T&x){
            x = 0;
            while(*col != '\0'){
                if('0' <= *col && *col <= '9'){
                    T y = *col - '0';
                    if(x > ((std::numeric_limits<T>::max)()-y)/10){
                        overflow_policy::on_overflow(x);
                        return;
                    }
                    x = 10*x+y;
                }else
                    throw error::no_digit();
                ++col;
            }
        }

        template<class overflow_policy>void parse(char*col, unsigned char &x)
        {parse_unsigned_integer<overflow_policy>(col, x);}
        template<class overflow_policy>void parse(char*col, unsigned short &x)
        {parse_unsigned_integer<overflow_policy>(col, x);}
        template<class overflow_policy>void parse(char*col, unsigned int &x)
        {parse_unsigned_integer<overflow_policy>(col, x);}
        template<class overflow_policy>void parse(char*col, unsigned long &x)
        {parse_unsigned_integer<overflow_policy>(col, x);}
        template<class overflow_policy>void parse(char*col, unsigned long long &x)
        {parse_unsigned_integer<overflow_policy>(col, x);}

        template<class overflow_policy, class T>
        void parse_signed_integer(const char*col, T&x){
            if(*col == '-'){
                ++col;

                x = 0;
                while(*col != '\0'){
                    if('0' <= *col && *col <= '9'){
                        T y = *col - '0';
                        if(x < ((std::numeric_limits<T>::min)()+y)/10){
                            overflow_policy::on_underflow(x);
                            return;
                        }
                        x = 10*x-y;
                    }else
                        throw error::no_digit();
                    ++col;
                }
                return;
            }else if(*col == '+')
                ++col;
            parse_unsigned_integer<overflow_policy>(col, x);
        }

        template<class overflow_policy>void parse(char*col, signed char &x)
        {parse_signed_integer<overflow_policy>(col, x);}
        template<class overflow_policy>void parse(char*col, signed short &x)
        {parse_signed_integer<overflow_policy>(col, x);}
        template<class overflow_policy>void parse(char*col, signed int &x)
        {parse_signed_integer<overflow_policy>(col, x);}
        template<class overflow_policy>void parse(char*col, signed long &x)
        {parse_signed_integer<overflow_policy>(col, x);}
        template<class overflow_policy>void parse(char*col, signed long long &x)
        {parse_signed_integer<overflow_policy>(col, x);}

        template<class T>
        void parse_float(const char*col, T&x){
            bool is_neg = false;
            if(*col == '-'){
                is_neg = true;
                ++col;
            }else if(*col == '+')
                ++col;

            x = 0;
            while('0' <= *col && *col <= '9'){
                int y = *col - '0';
                x *= 10;
                x += y;
                ++col;
            }

            if(*col == '.'|| *col == ','){
                ++col;
                T pos = 1;
                while('0' <= *col && *col <= '9'){
                    pos /= 10;
                    int y = *col - '0';
                    ++col;
                    x += y*pos;
                }
            }

            if(*col == 'e' || *col == 'E'){
                ++col;
                int e;

                parse_signed_integer<set_to_max_on_overflow>(col, e);

                if(e != 0){
                    T base;
                    if(e < 0){
                        base = T(0.1);
                        e = -e;
                    }else{
                        base = T(10);
                    }

                    while(e != 1){
                        if((e & 1) == 0){
                            base = base*base;
                            e >>= 1;
                        }else{
                            x *= base;
                            --e;
                        }
                    }
                    x *= base;
                }
            }else{
                if(*col != '\0')
                    throw error::no_digit();
            }

            if(is_neg)
                x = -x;
        }

        template<class overflow_policy> void parse(char*col, float&x) { parse_float(col, x); }
        template<class overflow_policy> void parse(char*col, double&x) { parse_float(col, x); }
        template<class overflow_policy> void parse(char*col, long double&x) { parse_float(col, x); }

        template<class overflow_policy, class T>
        void parse(char*col, T&x){
            // Mute unused variable compiler warning
            (void)col;
            (void)x;
            // GCC evalutes "false" when reading the template and
            // "sizeof(T)!=sizeof(T)" only when instantiating it. This is why
            // this strange construct is used.
//            static_assert(sizeof(T)!=sizeof(T),
//                          "Can not parse this type. Only buildin integrals, floats, char, char*, const char* and std::string are supported");
        }

    }

    template<unsigned column_count,
            class trim_policy = trim_chars<' ', '\t'>,
            class quote_policy = no_quote_escape<','>,
            class overflow_policy = throw_on_overflow,
            class comment_policy = no_comment
    >
    class CSVReader{
    private:
        LineReader in;

        char*row[column_count];
        std::string column_names[column_count];

        std::vector<int>col_order;

        template<class ...ColNames>
        void set_column_names(std::string s, ColNames...cols){
            column_names[column_count-sizeof...(ColNames)-1] = std::move(s);
            set_column_names(std::forward<ColNames>(cols)...);
        }

        void set_column_names(){}


    public:
        CSVReader() = delete;
        CSVReader(const CSVReader&) = delete;
        CSVReader&operator=(const CSVReader&);

        template<class ...Args>
        explicit CSVReader(Args&&...args):in(std::forward<Args>(args)...){
            std::fill(row, row+column_count, nullptr);
            col_order.resize(column_count);
            for(unsigned i=0; i<column_count; ++i)
                col_order[i] = i;
            for(unsigned i=1; i<=column_count; ++i)
                column_names[i-1] = "col"+std::to_string(i);
        }

        char*next_line(){
            return in.next_line();
        }

        template<class ...ColNames>
        void read_header(ignore_column ignore_policy, ColNames...cols){
            static_assert(sizeof...(ColNames)>=column_count, "not enough column names specified");
            static_assert(sizeof...(ColNames)<=column_count, "too many column names specified");
            try{
                set_column_names(std::forward<ColNames>(cols)...);

                char*line;
                do{
                    line = in.next_line();
                    if(!line)
                        throw error::header_missing();
                }while(comment_policy::is_comment(line));

                detail::parse_header_line
                        <column_count, trim_policy, quote_policy>
                        (line, col_order, column_names, ignore_policy);
            }catch(error::with_file_name&err){
                err.set_file_name(in.get_truncated_file_name());
                throw;
            }
        }

        template<class ...ColNames>
        void set_header(ColNames...cols){
            static_assert(sizeof...(ColNames)>=column_count,
                          "not enough column names specified");
            static_assert(sizeof...(ColNames)<=column_count,
                          "too many column names specified");
            set_column_names(std::forward<ColNames>(cols)...);
            std::fill(row, row+column_count, nullptr);
            col_order.resize(column_count);
            for(unsigned i=0; i<column_count; ++i)
                col_order[i] = i;
        }

        bool has_column(const std::string&name) const {
            return col_order.end() != std::find(
                    col_order.begin(), col_order.end(),
                    std::find(std::begin(column_names), std::end(column_names), name)
                    - std::begin(column_names));
        }

        void set_file_name(const std::string&file_name){
            in.set_file_name(file_name);
        }

        void set_file_name(const char*file_name){
            in.set_file_name(file_name);
        }

        const char*get_truncated_file_name()const{
            return in.get_truncated_file_name();
        }

        void set_file_line(unsigned file_line){
            in.set_file_line(file_line);
        }

        unsigned get_file_line()const{
            return in.get_file_line();
        }

    private:
        void parse_helper(std::size_t){}

        template<class T, class ...ColType>
        void parse_helper(std::size_t r, T&t, ColType&...cols){
            if(row[r]){
                try{
                    try{
                        ::io::detail::parse<overflow_policy>(row[r], t);
                    }catch(error::with_column_content&err){
                        err.set_column_content(row[r]);
                        throw;
                    }
                }catch(error::with_column_name&err){
                    err.set_column_name(column_names[r].c_str());
                    throw;
                }
            }
            parse_helper(r+1, cols...);
        }


    public:
        template<class ...ColType>
        bool read_row(ColType& ...cols){
            static_assert(sizeof...(ColType)>=column_count,
                          "not enough columns specified");
            static_assert(sizeof...(ColType)<=column_count,
                          "too many columns specified");
            try{
                try{

                    char*line;
                    do{
                        line = in.next_line();
                        if(!line)
                            return false;
                    }while(comment_policy::is_comment(line));

                    detail::parse_line<trim_policy, quote_policy>
                            (line, row, col_order);

                    parse_helper(0, cols...);
                }catch(error::with_file_name&err){
                    err.set_file_name(in.get_truncated_file_name());
                    throw;
                }
            }catch(error::with_file_line&err){
                err.set_file_line(in.get_file_line());
                throw;
            }

            return true;
        }
    };
}
#endif
